﻿#region Imports
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using CommonUtilities.Enums;
using CommonUtilities.Extensions;
#endregion

namespace CommonUtilities.Attributes
{
    /// <summary>
    /// Generates property lists and mappings as attributed to
    /// </summary>
    /// <typeparam name="TSource">Class type where properties to be analysed</typeparam>
    public static class AttributeLists<TSource>
        where TSource : class, new()
    {
        #region Properties
        #region Property - attribute value mappings
        /// <summary>
        /// Property and display name mapping dictionary
        /// <para>(Note: returns property name, if not defined)</para>
        /// </summary>
        public static Dictionary<string, string> PropertyMapDisplayName { get { return GeneratePropertyAttributeValueMap<DisplayNameAttribute>("DisplayName", true); } }

        /// <summary>
        /// Property and data import name name mapping dictionary
        /// </summary>
        public static Dictionary<string, string> PropertyMapDataImportName { get { return GeneratePropertyAttributeValueMap<DataImportMapAttribute>(DataImportMapAttribute.DefaultPropertyName, false); } }

        /// <summary>
        /// Property and data import name name mapping dictionary
        /// <para>(Note: returns property name, if not defined)</para>
        /// </summary>
        public static Dictionary<string, string> PropertyMapDataImportNameSafe { get { return GeneratePropertyAttributeValueMap<DataImportMapAttribute>(DataImportMapAttribute.DefaultPropertyName, true); } }

        /// <summary>
        /// Property and description mapping dictionary (for use as tooltip)
        /// <para>(Note: returns property name, if not defined)</para>
        /// </summary>
        public static Dictionary<string, string> PropertyMapDescription { get { return GeneratePropertyAttributeValueMap<DescriptionAttribute>("Description", true); } }

        public static Dictionary<string, int> PropertyMapSortIndex
        {
            get
            {
                Dictionary<string, int> result = GeneratePropertyAttributeValueMap<SortIndexAttribute>(SortIndexAttribute.DefaultPropertyName, true)
                    .Select(map => new KeyValuePair<string, int>(map.Key, map.Value.IsNumeric() ? map.Value.ChangeType<int>() : -1))
                    .ToDictionary(o => o.Key, o => o.Value);

                ObservableCollection<string> browsableFields = PropertyFieldsBrowsable;
                List<string> removeKeys = result.Keys._Where(key => !browsableFields._Contains(key)).ToList();
                foreach (string key in removeKeys) result.Remove(key);

                List<string> addKeys = browsableFields._Where(field => !result.Keys._Contains(field)).ToList();
                foreach (string field in addKeys) result.Add(field, result.Values._Max() + 1);

                int sortIndex = 0;
                foreach (KeyValuePair<string, int> pair in result._OrderBy(pair => pair.Value))
                    result[pair.Key] = sortIndex++;

                return result;
            }
        }

        /// <summary>
        /// Property and constraint mapping dictionary
        /// <para>(Note: returns property name, if not defined)</para>
        /// </summary>
        public static Dictionary<string, eConstraint> PropertyMapConstraint
        {
            get
            {
                Dictionary<string, eConstraint> result = GeneratePropertyAttributeValueMap<ConstraintAttribute>(ConstraintAttribute.DefaultPropertyName, true)
                    .Select(map => new KeyValuePair<string, eConstraint>(map.Key, ConvertToConstraint(map.Value)))
                    .ToDictionary(o => o.Key, o => o.Value);

                return result;
            }
        }

        private static eConstraint ConvertToConstraint(string sConstraint)
        {
            foreach (eConstraint constraint in ConstraintAttribute.Default.Constraint._GetValues())
                if (constraint.ToString() == sConstraint) return constraint;

            return ConstraintAttribute.DefaultValueOnAbsent;
        }
        #endregion Property - attribute value mappings

        #region Property lists
        /// <summary>
        /// List of property names that are filterable
        /// </summary>
        public static ObservableCollection<string> PropertyFieldsFilterable { get { return GeneratePropertyList<FilterableAttribute>(FilterableAttribute.DefaultPropertyName, FilterableAttribute.DefaultValueOnAbsent); } }

        /// <summary>
        /// List of property names that are browsable
        /// </summary>
        public static ObservableCollection<string> PropertyFieldsBrowsable { get { return GeneratePropertyList<BrowsableAttribute>("Browsable", BrowsableAttribute.Default.Browsable); } }

        /// <summary>
        /// List of property names that are unique
        /// </summary>
        public static ObservableCollection<string> PropertyFieldsUnique { get { return GeneratePropertyList<UniqueAttribute>(UniqueAttribute.DefaultPropertyName, UniqueAttribute.DefaultValueOnAbsent); } }

        /// <summary>
        /// List of property names that are sortable
        /// </summary>
        public static ObservableCollection<string> PropertyFieldsSortable { get { return GeneratePropertyList<SortableAttribute>(SortableAttribute.DefaultPropertyName, SortableAttribute.DefaultValueOnAbsent); } }
        #endregion Property lists

        #region Valid property-attribute pairs
        /// <summary>
        /// Property name and display name mapping dictionary for filterable properties
        /// </summary>
        /// <returns>Filter dictionary with property name and display name</returns>
        public static Dictionary<string, string> PropertyMapFilterable { get { return GenerateValidPropertyAttributePair(PropertyFieldsFilterable, PropertyMapDisplayName); } }

        /// <summary>
        /// Property name and display name mapping dictionary for sortable properties
        /// </summary>
        /// <returns>Filter dictionary with property name and display name</returns>
        public static Dictionary<string, string> PropertyMapSortable { get { return GenerateValidPropertyAttributePair(PropertyFieldsSortable, PropertyMapDisplayName); } }
        #endregion Valid property-attribute pairs
        #endregion Properties

        #region Methods
        #region Generic implementations
        /// <summary>
        /// Generates mapping dictionary of property-attribute pair
        /// for valid properties only (as attributed)
        /// </summary>
        /// <param name="properties">Valid properties</param>
        /// <param name="map">Property-attribute mapping</param>
        /// <returns></returns>
        private static Dictionary<string, string> GenerateValidPropertyAttributePair(
            ObservableCollection<string> properties, Dictionary<string, string> map)
        {
            Dictionary<string, string> allFilters = new Dictionary<string, string>();
            map._Where(dict => properties._Contains(dict.Key))
                ._ForEach(dict => allFilters.AddToDictionary(dict.Key, dict.Value));

            return allFilters;
        }

        /// <summary>
        /// Generates list of property names that satisfies the value
        /// for specified property &quot;<paramref name="attribPropName"/>&quot;
        /// of the attribute &quot;<paramref name="attribName"/>&quot;
        /// </summary>
        /// <param name="attribName">Attribute name</param>
        /// <param name="attribPropName">Property name of attribute to check for</param>
        /// <param name="defaultValue">Default value to return when property not attributed to</param>
        /// <returns>Generates list of property names that satisfies the specified attribute property value</returns>
        private static ObservableCollection<string> GeneratePropertyList(string attribName, string attribPropName, bool defaultValue)
        {
            TSource tmp = new TSource { };

            return tmp.GetType().GetProperties()
                ._Where(prop => tmp.GetPropertyAttributeProperty<TSource, bool>(prop.Name, attribName, attribPropName, defaultValue))
                ._Select(prop => prop.Name).ToObservableCollection();
        }

        /// <summary>
        /// Generates list of property names that satisfies the value
        /// for specified property &quot;<paramref name="attribPropName"/>&quot;
        /// of the attribute &quot;<paramref name="attribName"/>&quot;
        /// </summary>
        /// <typeparam name="TAttrib">Attribute type</typeparam>
        /// <param name="attribPropName">Property name of attribute to check for</param>
        /// <param name="defaultValue">Default value to return when property not attributed to</param>
        /// <returns>Generates list of property names that satisfies the specified attribute property value</returns>
        private static ObservableCollection<string> GeneratePropertyList<TAttrib>(string attribPropName, bool defaultValue)
        {
            return GeneratePropertyList(typeof(TAttrib).Name, attribPropName, defaultValue);
        }

        /// <summary>
        /// Property and display name mapping dictionary
        /// </summary>
        /// <param name="attribName">Attribute name</param>
        /// <param name="attribPropName">Attribute property name</param>
        /// <param name="generateMissing">TRUE: Generates mapping for properties not attributed to
        /// <para>(Note: returns property name, if not defined)</para></param>
        /// <returns>Generates property and display name mapping dictionary</returns>
        private static Dictionary<string, string> GeneratePropertyAttributeValueMap(string attribName, string attribPropName, bool generateMissing)
        {
            TSource tmp = new TSource { };
            Dictionary<string, string> dictPropertyAttributeValueMap = new Dictionary<string, string>();

            tmp.GetType().GetProperties()
                ._Select(prop => new { Key = prop.Name, Value = tmp.GetPropertyAttributeProperty<TSource, string>(prop.Name, attribName, attribPropName, generateMissing ? prop.Name : null) })
                ._Where(dict => generateMissing || !dict.Value.IsNull())
                ._ForEach(dict => dictPropertyAttributeValueMap.AddToDictionary(dict.Key, dict.Value));

            return dictPropertyAttributeValueMap;
        }

        /// <summary>
        /// Property and display name mapping dictionary
        /// </summary>
        /// <typeparam name="TAttrib">Attribute type</typeparam>
        /// <param name="attribPropName">Attribute property name</param>
        /// <param name="generateMissing">TRUE: Generates mapping for properties not attributed to
        /// <para>(Note: returns property name, if not defined)</para></param>
        /// <returns>Generates property and display name mapping dictionary</returns>
        private static Dictionary<string, string> GeneratePropertyAttributeValueMap<TAttrib>(string attribPropName, bool generateMissing)
            where TAttrib : Attribute
        {
            IEnumerable<KeyValuePair<string, string>> result
                = GeneratePropertyAttributeValueMap(typeof(TAttrib).Name, attribPropName, generateMissing);

            return result.ToDictionary(dict => dict.Key, dict => dict.Value);
        }
        #endregion Generic implementations
        #endregion Methods
    }
}
